from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
import json
import os
import threading
import time
from collections import Counter, defaultdict
import pickle
import click
# 导入推荐系统
from models.recommendation_system import ImprovedTieredRecommendationSystem

app = Flask(__name__)
DB_USER = 'root'
DB_PWD = '123456'
DB_HOST = 'localhost'
DB_PORT = '3306'
DB_NAME = 'recommend'

app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-key-change-me')
app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{DB_USER}:{DB_PWD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# 全局推荐系统实例
rec_system = None
movie_data = None
system_stats = {
    'total_users': 0,
    'total_recommendations': 0,
    'total_interactions': 0,
    'system_health': 'Initializing...'
}


# ==================== 数据库模型 ====================

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)
    user_type = db.Column(db.String(50), default='balanced')
    exploration_score = db.Column(db.Float, default=0.3)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)


class UserInteraction(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    movie_id = db.Column(db.Integer, nullable=False)
    interaction_type = db.Column(db.String(50), nullable=False)  # 'view', 'like', 'dislike', 'rating'
    interaction_value = db.Column(db.Float)  # 评分值或其他数值
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)
    session_id = db.Column(db.String(100))


class RecommendationLog(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    movie_ids = db.Column(db.Text, nullable=False)  # JSON格式存储推荐列表
    algorithm_version = db.Column(db.String(50), default='improved_tiered')
    recommendation_pools = db.Column(db.Text)  # JSON格式存储各池分布
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)


class SystemMetrics(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    metric_name = db.Column(db.String(100), nullable=False)
    metric_value = db.Column(db.Float, nullable=False)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)


# ==================== 推荐系统初始化 ====================

def initialize_recommendation_system():
    """初始化推荐系统"""
    global rec_system, movie_data, system_stats

    try:
        print("正在初始化推荐系统...")
        system_stats['system_health'] = 'Initializing models...'

        # 检查模型文件是否存在
        model_path = 'models/trained_system.pkl'
        if os.path.exists(model_path):
            print("加载已训练的模型...")
            with open(model_path, 'rb') as f:
                rec_system = pickle.load(f)
            movie_data = pd.read_csv('data/merged_df.csv')
        else:
            print("训练新的推荐系统...")
            rec_system = ImprovedTieredRecommendationSystem(sample_ratio=0.1, fast_mode=True)
            movie_data = rec_system.train_improved_system('data/merged_df.csv')

            # 保存训练好的模型
            with open(model_path, 'wb') as f:
                pickle.dump(rec_system, f)

        system_stats['system_health'] = 'Online'
        system_stats['total_users'] = movie_data['userId'].nunique()
        print("推荐系统初始化完成！")

    except Exception as e:
        print(f"推荐系统初始化失败: {e}")
        system_stats['system_health'] = f'Error: {str(e)}'


# ==================== 路由定义 ====================
import json

@app.template_filter('from_json')
def from_json_filter(value):
    """将JSON字符串转换为Python对象"""
    try:
        return json.loads(value) if value else {}
    except:
        return {}


@app.route('/')
def index():
    """首页"""
    if 'user_id' in session:
        return redirect(url_for('recommendations'))
    return render_template('index.html')


@app.route('/login', methods=['GET', 'POST'])
def login():
    """用户登录"""
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        user = User.query.filter_by(username=username).first()

        if user and user.check_password(password):
            session['user_id'] = user.id
            session['username'] = user.username
            flash('登录成功！', 'success')
            return redirect(url_for('recommendations'))
        else:
            flash('用户名或密码错误！', 'error')

    return render_template('login.html')


@app.route('/register', methods=['GET', 'POST'])
def register():
    """用户注册"""
    if request.method == 'POST':
        username = request.form['username']
        email = request.form['email']
        password = request.form['password']

        if User.query.filter_by(username=username).first():
            flash('用户名已存在！', 'error')
        elif User.query.filter_by(email=email).first():
            flash('邮箱已被注册！', 'error')
        else:
            user = User(username=username, email=email)
            user.set_password(password)
            db.session.add(user)
            db.session.commit()
            flash('注册成功！请登录。', 'success')
            return redirect(url_for('login'))

    return render_template('login.html', register=True)


@app.route('/logout')
def logout():
    """用户登出"""
    session.clear()
    flash('已成功登出！', 'info')
    return redirect(url_for('index'))


@app.route('/recommendations')
def recommendations():
    """推荐页面"""
    if 'user_id' not in session:
        return redirect(url_for('login'))

    def get_pool_mappings():
        """获取池子名称和颜色映射"""
        pool_names = {
            'cold_start': '冷启动池',
            'test': '测试池',
            'growth': '成长池',
            'mature': '成熟池',
            'long_tail': '长尾池',
            'unknown': '未知'
        }

        pool_color = {
            'cold_start': 'info',
            'test': 'warning',
            'growth': 'success',
            'mature': 'primary',
            'long_tail': 'danger',
            'unknown': 'secondary'
        }

        return pool_names, pool_color

    pool_names, pool_color = get_pool_mappings()

    if rec_system is None:
        flash('推荐系统正在初始化，请稍后再试', 'warning')
        return render_template('recommendations.html',
                               movies=[],
                               system_ready=False,
                               pool_names=pool_names,
                               pool_color=pool_color)

    user_id = session['user_id']

    try:
        print(f"Generating recommendations for user {user_id}")

        # 获取推荐
        recommended_movie_ids = rec_system.improved_recommend(user_id, top_k=12)
        print(f"Recommended movie IDs: {recommended_movie_ids}")

        # 获取电影详细信息
        movies = []
        pool_distribution = Counter()

        for i, movie_id in enumerate(recommended_movie_ids):
            print(f"Processing movie {i + 1}/{len(recommended_movie_ids)}: {movie_id}")

            try:
                movie_info = get_movie_details(movie_id)
                if movie_info:
                    # 验证必要字段
                    required_fields = ['avg_rating', 'rating_count', 'quality_score', 'pool', 'genres']
                    missing_fields = [field for field in required_fields if field not in movie_info]

                    if missing_fields:
                        print(f"Warning: Movie {movie_id} missing fields: {missing_fields}")
                        # 补充缺失字段
                        for field in missing_fields:
                            if field in ['avg_rating', 'rating_count', 'quality_score']:
                                movie_info[field] = 0
                            elif field == 'pool':
                                movie_info[field] = 'unknown'
                            elif field == 'genres':
                                movie_info[field] = []

                    movies.append(movie_info)
                    pool_distribution[movie_info.get('pool', 'unknown')] += 1
                    print(f"Successfully processed movie {movie_id}")
                else:
                    print(f"Warning: Could not get details for movie {movie_id}")

            except Exception as movie_error:
                print(f"Error processing movie {movie_id}: {movie_error}")
                import traceback
                traceback.print_exc()
                continue

        print(f"Successfully processed {len(movies)} movies")
        print(f"Pool distribution: {dict(pool_distribution)}")

        # 记录推荐日志
        if movies:  # 只有当有电影时才记录
            log_recommendation(user_id, recommended_movie_ids, dict(pool_distribution))

            # 更新系统统计
            system_stats['total_recommendations'] += 1

        return render_template('recommendations.html',
                               movies=movies,
                               pool_distribution=dict(pool_distribution),
                               pool_names=pool_names,
                               pool_color=pool_color,
                               system_ready=True)

    except Exception as e:
        print(f"Error in recommendations route: {e}")
        import traceback
        traceback.print_exc()

        flash(f'生成推荐时出错: {str(e)}', 'error')
        return render_template('recommendations.html',
                               movies=[],
                               pool_names=pool_names,
                               pool_color=pool_color,
                               system_ready=False)


@app.route('/movie/<int:movie_id>')
def movie_detail(movie_id):
    """电影详情页"""
    if 'user_id' not in session:
        return redirect(url_for('login'))

    # 定义池子名称和颜色映射
    pool_names = {
        'cold_start': '冷启动池',
        'test': '测试池',
        'growth': '成长池',
        'mature': '成熟池',
        'long_tail': '长尾池'
    }

    pool_color = {
        'cold_start': 'info',
        'test': 'warning',
        'growth': 'success',
        'mature': 'primary',
        'long_tail': 'danger'
    }

    movie_info = get_movie_details(movie_id)
    if not movie_info:
        flash('电影不存在！', 'error')
        return redirect(url_for('recommendations'))

    # 记录用户查看行为
    record_interaction(session['user_id'], movie_id, 'view')

    # 获取相似电影推荐
    similar_movies = get_similar_movies(movie_id, limit=6)

    return render_template('movie_detail.html',
                           movie=movie_info,
                           similar_movies=similar_movies,
                           pool_names=pool_names,
                           pool_color=pool_color)


@app.route('/user_profile')
def user_profile():
    """用户画像页面"""
    if 'user_id' not in session:
        return redirect(url_for('login'))

    user_id = session['user_id']
    user = User.query.get(user_id)

    # 获取用户偏好分析
    user_preferences = {}
    if rec_system and user_id in rec_system.user_preferences:
        user_preferences = rec_system.user_preferences[user_id]

    # 获取用户交互历史
    interactions = UserInteraction.query.filter_by(user_id=user_id).order_by(UserInteraction.timestamp.desc()).limit(
        20).all()

    # 获取推荐历史
    recommendation_logs = RecommendationLog.query.filter_by(user_id=user_id).order_by(
        RecommendationLog.timestamp.desc()).limit(10).all()

    return render_template('user_profile.html',
                           user=user,
                           preferences=user_preferences,
                           interactions=interactions,
                           recommendation_logs=recommendation_logs)


@app.route('/admin')
def admin_dashboard():
    """管理员仪表板"""
    if 'user_id' not in session:
        return redirect(url_for('login'))

    # 简单的管理员验证（实际应用中应该有更严格的权限控制）
    user = User.query.get(session['user_id'])
    if user.username not in ['admin', 'administrator']:
        flash('权限不足！', 'error')
        return redirect(url_for('recommendations'))

    # 获取系统统计信息
    stats = get_system_statistics()

    # 获取最近的系统指标
    recent_metrics = SystemMetrics.query.order_by(SystemMetrics.timestamp.desc()).limit(50).all()

    # 获取用户活跃度
    user_activity = get_user_activity_stats()

    # 获取推荐系统性能指标
    recommendation_stats = get_recommendation_performance()

    return render_template('admin_dashboard.html',
                           stats=stats,
                           metrics=recent_metrics,
                           user_activity=user_activity,
                           recommendation_stats=recommendation_stats,
                           system_health=system_stats['system_health'])


# ==================== API接口 ====================

@app.route('/api/rate_movie', methods=['POST'])
def rate_movie():
    """电影评分API"""
    if 'user_id' not in session:
        return jsonify({'error': 'Not logged in'}), 401

    data = request.get_json()
    movie_id = data.get('movie_id')
    rating = data.get('rating')

    if not movie_id or not rating:
        return jsonify({'error': 'Missing parameters'}), 400

    if rating < 1 or rating > 5:
        return jsonify({'error': 'Rating must be between 1 and 5'}), 400

    # 记录评分
    record_interaction(session['user_id'], movie_id, 'rating', rating)

    # 更新测试池表现（如果是测试池电影）
    if rec_system and movie_id in rec_system.test_pool_performance:
        rec_system.update_test_pool_performance(movie_id, {'clicked': True, 'rating': rating})

    return jsonify({'success': True, 'message': '评分已记录'})


@app.route('/api/like_movie', methods=['POST'])
def like_movie():
    """点赞电影API"""
    if 'user_id' not in session:
        return jsonify({'error': 'Not logged in'}), 401

    data = request.get_json()
    movie_id = data.get('movie_id')

    if not movie_id:
        return jsonify({'error': 'Missing movie_id'}), 400

    record_interaction(session['user_id'], movie_id, 'like')

    # 更新测试池表现
    if rec_system and movie_id in rec_system.test_pool_performance:
        rec_system.update_test_pool_performance(movie_id, {'clicked': True})

    return jsonify({'success': True, 'message': '已添加到喜欢列表'})


@app.route('/api/system_stats')
def get_system_stats():
    """获取系统统计信息API"""
    return jsonify(system_stats)


@app.route('/api/recommendation_explanation/<int:user_id>')
def recommendation_explanation(user_id):
    """推荐解释API"""
    if 'user_id' not in session or session['user_id'] != user_id:
        return jsonify({'error': 'Unauthorized'}), 403

    if not rec_system:
        return jsonify({'error': 'System not ready'}), 503

    try:
        # 获取用户偏好
        user_pref = rec_system.user_preferences.get(user_id, {})

        # 获取最近的推荐日志
        recent_log = RecommendationLog.query.filter_by(user_id=user_id).order_by(
            RecommendationLog.timestamp.desc()).first()

        explanation = {
            'user_type': user_pref.get('user_type', 'unknown'),
            'exploration_score': user_pref.get('exploration_score', 0),
            'preferred_genres': user_pref.get('preferred_genres', []),
            'pool_distribution': json.loads(recent_log.recommendation_pools) if recent_log else {},
            'explanation_text': generate_explanation_text(user_pref)
        }

        return jsonify(explanation)

    except Exception as e:
        return jsonify({'error': str(e)}), 500


# ==================== 辅助函数 ====================

def get_movie_details(movie_id):
    """获取电影详细信息"""
    if movie_data is None or rec_system is None:
        return None

    try:
        # 从推荐系统获取基础信息
        movie_info = rec_system.get_movie_info(movie_id, movie_data)
        if not movie_info:
            return None

        # 从电影特征数据中获取详细信息
        movie_features = rec_system.movie_features[rec_system.movie_features['movieId'] == movie_id]

        if not movie_features.empty:
            feature_row = movie_features.iloc[0]
            # 添加详细的数值字段
            movie_info.update({
                'avg_rating': float(feature_row.get('avg_rating', 0)),
                'rating_count': int(feature_row.get('rating_count', 0)),
                'quality_score': float(feature_row.get('quality_score', 0)),
                'rating_std': float(feature_row.get('rating_std', 1.0)),
                'rating_velocity': float(feature_row.get('rating_velocity', 0)),
                'genre_count': int(feature_row.get('genre_count', 0)),
                'user_penetration': float(feature_row.get('user_penetration', 0)),
                'rating_range': float(feature_row.get('rating_range', 0))
            })
        else:
            # 提供默认值
            movie_info.update({
                'avg_rating': 3.0,
                'rating_count': 0,
                'quality_score': 3.0,
                'rating_std': 1.0,
                'rating_velocity': 0.0,
                'genre_count': 1,
                'user_penetration': 0.0,
                'rating_range': 0.0
            })

        # 从原始数据中获取电影标题等信息
        movie_row = movie_data[movie_data['movieId'] == movie_id]
        if not movie_row.empty:
            movie_info['title'] = movie_row.iloc[0].get('title', f'Movie {movie_id}')
            movie_info['year'] = movie_row.iloc[0].get('year', 'Unknown')
        else:
            movie_info['title'] = f'Movie {movie_id}'
            movie_info['year'] = 'Unknown'

        # 确保 genres 字段存在且为列表
        if 'genres' not in movie_info or not isinstance(movie_info['genres'], list):
            movie_info['genres'] = []

        # 确保 pool 字段存在
        if 'pool' not in movie_info:
            movie_info['pool'] = 'unknown'

        print(f"Movie {movie_id} details: {movie_info}")  # 调试信息

        return movie_info

    except Exception as e:
        print(f"获取电影信息出错: {e}")
        import traceback
        traceback.print_exc()

        # 返回最基本的信息作为fallback
        return {
            'movieId': movie_id,
            'title': f'Movie {movie_id}',
            'year': 'Unknown',
            'pool': 'unknown',
            'genres': [],
            'avg_rating': 3.0,
            'rating_count': 0,
            'quality_score': 3.0,
            'rating_std': 1.0,
            'rating_velocity': 0.0,
            'genre_count': 1,
            'user_penetration': 0.0,
            'rating_range': 0.0,
            'info': 'No information available'
        }


def get_similar_movies(movie_id, limit=6):
    """获取相似电影"""
    if not rec_system or not hasattr(rec_system, 'movie_similarity_matrix'):
        return []

    try:
        movie_list = list(rec_system.movie_features['movieId'])
        if movie_id not in movie_list:
            return []

        movie_idx = movie_list.index(movie_id)
        similarities = rec_system.movie_similarity_matrix[movie_idx]

        # 获取最相似的电影
        similar_indices = np.argsort(similarities)[::-1][1:limit + 1]  # 排除自己
        similar_movie_ids = [movie_list[idx] for idx in similar_indices]

        similar_movies = []
        for sim_movie_id in similar_movie_ids:
            movie_info = get_movie_details(sim_movie_id)
            if movie_info:
                similar_movies.append(movie_info)

        return similar_movies

    except Exception as e:
        print(f"获取相似电影出错: {e}")
        return []


def record_interaction(user_id, movie_id, interaction_type, value=None):
    """记录用户交互"""
    interaction = UserInteraction(
        user_id=user_id,
        movie_id=movie_id,
        interaction_type=interaction_type,
        interaction_value=value,
        session_id=session.get('session_id')
    )
    db.session.add(interaction)
    db.session.commit()

    # 更新系统统计
    system_stats['total_interactions'] += 1


def log_recommendation(user_id, movie_ids, pool_distribution):
    """记录推荐日志"""
    log = RecommendationLog(
        user_id=user_id,
        movie_ids=json.dumps(movie_ids),
        recommendation_pools=json.dumps(pool_distribution),
        algorithm_version='improved_tiered_v1.0'
    )
    db.session.add(log)
    db.session.commit()


def get_system_statistics():
    """获取系统统计信息"""
    try:
        stats = {
            'total_users': User.query.count(),
            'total_interactions': UserInteraction.query.count(),
            'total_recommendations': RecommendationLog.query.count(),
            'active_users_today': UserInteraction.query.filter(
                UserInteraction.timestamp >= datetime.now() - timedelta(days=1)
            ).distinct(UserInteraction.user_id).count(),
            'system_health': system_stats['system_health']
        }

        if rec_system:
            stats.update({
                'cold_start_movies': len(rec_system.movie_pools.get('cold_start', [])),
                'test_movies': len(rec_system.movie_pools.get('test', [])),
                'growth_movies': len(rec_system.movie_pools.get('growth', [])),
                'mature_movies': len(rec_system.movie_pools.get('mature', [])),
                'long_tail_movies': len(rec_system.movie_pools.get('long_tail', []))
            })

        return stats

    except Exception as e:
        return {'error': str(e)}


def get_user_activity_stats():
    """获取用户活跃度统计"""
    try:
        # 最近7天的用户活跃度
        activity = []
        for i in range(7):
            date = datetime.now() - timedelta(days=i)
            count = UserInteraction.query.filter(
                UserInteraction.timestamp >= date.replace(hour=0, minute=0, second=0),
                UserInteraction.timestamp < date.replace(hour=23, minute=59, second=59)
            ).distinct(UserInteraction.user_id).count()

            activity.append({
                'date': date.strftime('%Y-%m-%d'),
                'active_users': count
            })

        return activity[::-1]  # 反转，最早的日期在前

    except Exception as e:
        return []


def get_recommendation_performance():
    """获取推荐系统性能统计"""
    try:
        # 各池推荐分布
        recent_logs = RecommendationLog.query.filter(
            RecommendationLog.timestamp >= datetime.now() - timedelta(days=7)
        ).all()

        pool_stats = defaultdict(int)
        for log in recent_logs:
            pools = json.loads(log.recommendation_pools)
            for pool, count in pools.items():
                pool_stats[pool] += count

        # 用户类型分布
        user_type_stats = Counter()
        if rec_system:
            for user_pref in rec_system.user_preferences.values():
                user_type_stats[user_pref.get('user_type', 'unknown')] += 1

        return {
            'pool_distribution': dict(pool_stats),
            'user_type_distribution': dict(user_type_stats),
            'avg_interactions_per_user': UserInteraction.query.count() / max(User.query.count(), 1)
        }

    except Exception as e:
        return {}


def generate_explanation_text(user_pref):
    """生成推荐解释文本"""
    user_type = user_pref.get('user_type', 'balanced')
    exploration_score = user_pref.get('exploration_score', 0.3)
    preferred_genres = user_pref.get('preferred_genres', [])

    explanations = {
        'explorer': '您是探索型用户，我们为您推荐了更多长尾和小众电影来满足您的探索需求。',
        'mainstream': '您偏好主流内容，我们为您推荐了经过验证的热门电影。',
        'early_adopter': '您喜欢尝试新内容，我们为您推荐了一些测试池中的潜力电影。',
        'niche_lover': '您偏爱小众内容，我们特别为您推荐了高质量的长尾电影。',
        'balanced': '基于您的观影历史，我们为您提供了平衡的推荐组合。'
    }

    base_text = explanations.get(user_type, explanations['balanced'])

    if preferred_genres:
        genre_text = f"考虑到您偏好 {', '.join(preferred_genres[:2])} 等类型，"
        base_text = genre_text + base_text

    if exploration_score > 0.6:
        base_text += " 我们还添加了一些您可能感兴趣的新发现。"

    return base_text


# ==================== 应用启动 ====================


def create_tables():
    """创建数据库表"""
    db.create_all()


if __name__ == '__main__':
    # 初始化数据库
    with app.app_context():
        create_tables()  # 只在启动时执行一次
    # 在后台线程中初始化推荐系统
    init_thread = threading.Thread(target=initialize_recommendation_system)
    init_thread.start()

    # 启动Flask应用
    app.run(debug=True, host='0.0.0.0', port=5000)